package com.linqingying.cangjie.ide.run.cjpm

import com.linqingying.cangjie.CangJieBundle
import com.linqingying.cangjie.cjpm.project.model.CjpmProject
import com.linqingying.cangjie.cjpm.project.model.cjpmProjects
import com.linqingying.cangjie.cjpm.project.settings.cangjieSettings
import com.linqingying.cangjie.cjpm.toolchain.CjToolchainBase
import com.linqingying.cangjie.cjpm.toolchain.tools.Cjpm
import com.linqingying.cangjie.ide.run.CjCommandConfiguration
import com.linqingying.cangjie.ide.run.cjpm.runconfig.CjLanguageRuntimeConfiguration
import com.linqingying.cangjie.ide.run.cjpm.runconfig.CjLanguageRuntimeType
import com.linqingying.cangjie.ide.run.hasRemoteTarget
import com.intellij.execution.Executor
import com.intellij.execution.InputRedirectAware
import com.intellij.execution.configuration.EnvironmentVariablesData
import com.intellij.execution.configurations.*
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.target.LanguageRuntimeType
import com.intellij.execution.target.TargetEnvironmentAwareRunProfile
import com.intellij.execution.target.TargetEnvironmentConfiguration
import com.intellij.execution.testframework.actions.ConsolePropertiesProvider
import com.intellij.execution.util.ProgramParametersUtil
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.options.SettingsEditor
import com.intellij.openapi.options.SettingsEditorGroup
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.util.execution.ParametersListUtil
import com.linqingying.cangjie.ide.run.CangJieCommandLineState
import org.jdom.Element
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths

val isUnitTestMode: Boolean get() = ApplicationManager.getApplication().isUnitTestMode

val Project.toolchain: CjToolchainBase?
    get() {
        val toolchain = cangjieSettings.state.toolchain
        return when {
            toolchain != null -> toolchain
            isUnitTestMode -> CjToolchainBase.suggest()
            else -> null
        }
    }

enum class BuildTarget {
    LOCAL, REMOTE;

    val isLocal: Boolean get() = this == LOCAL
    val isRemote: Boolean get() = this == REMOTE
}

class CjpmCommandConfiguration(project: Project, factory: ConfigurationFactory, name: String) :
    CjCommandConfiguration(project, name, factory),
    InputRedirectAware.InputRedirectOptions,
    ConsolePropertiesProvider,
    TargetEnvironmentAwareRunProfile {

    override var command: String = "run"
    override fun checkConfiguration() {

        if (isRedirectInput) {
            val file = redirectInputFile
            when {
                file?.exists() != true -> throw RuntimeConfigurationWarning(CangJieBundle.message("dialog.message.input.file.doesn.t.exist"))
                !file.isFile -> throw RuntimeConfigurationWarning(CangJieBundle.message("dialog.message.input.file.not.valid"))
            }
        }
        val config = clean()
        if (config is CleanConfiguration.Err) throw config.error
        config as CleanConfiguration.Ok

        // TODO: remove when `com.intellij.execution.process.ElevationService` supports error stream redirection

        if (withSudo && showTestToolWindow(config.cmd)) {
            val message = if (SystemInfo.isWindows) {
                CangJieBundle.message("notification.run.tests.as.root.windows")
            } else {
                CangJieBundle.message("notification.run.tests.as.root.unix")
            }
            throw RuntimeConfigurationWarning(message)
        }
    }

    private fun showTestToolWindow(commandLine: CjpmCommandLine): Boolean = when {

        commandLine.command !in listOf("test" ) -> false

        Cjpm.TEST_NOCAPTURE_ENABLED_KEY.asBoolean() -> false
        else -> !hasRemoteTarget
    }

    private val redirectInputFile: File?
        get() {
            if (!isRedirectInput) return null
            if (redirectInputPath?.isNotEmpty() != true) return null
            val redirectInputPath = FileUtil.toSystemDependentName(
                ProgramParametersUtil.expandPathAndMacros(
                    redirectInputPath,
                    null,
                    project
                )
            )
            val file = File(redirectInputPath)

            return file
        }

    var buildTarget: BuildTarget = BuildTarget.REMOTE

    sealed class CleanConfiguration {

        class Ok(
            val cmd: CjpmCommandLine,
            val toolchain: CjToolchainBase
        ) : CleanConfiguration()

        class Err(val error: RuntimeConfigurationError) : CleanConfiguration()

        val ok: Ok? get() = this as? Ok

        companion object {
            fun error(@NlsContexts.DialogMessage message: String) =
                Err(RuntimeConfigurationError(message))
        }
    }

    var args: String? = null


    var requiredFeatures: Boolean = true
    var allFeatures: Boolean = false
    var withSudo: Boolean = false


    var env: EnvironmentVariablesData = EnvironmentVariablesData.DEFAULT
    private fun CjpmCommandLine.toRawCommand(): String {
        val toolchainOverride = toolchain?.let { "+$it" }
        return ParametersListUtil.join(listOfNotNull(toolchainOverride, command, *additionalArguments.toTypedArray()))
    }

    fun setFromCmd(cmd: CjpmCommandLine) {
        command = cmd.toRawCommand()
        requiredFeatures = cmd.requiredFeatures
        allFeatures = cmd.allFeatures

        withSudo = cmd.withSudo

        env = cmd.environmentVariables

    }


    var executorId: String = "Run"

    override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState? {

        executorId = executor.id


        val config = clean().ok ?: return null
//        运行命令
//
//        if(config.cmd.command == "run"){
//            return CangJieCommandLineState(environment, this, config)
//        }

        return CjpmRunState(environment, this, config,)

    }

    fun clean(): CleanConfiguration {
        val workingDirectory = project.basePath?.let { Paths.get(it) }
            ?: return CleanConfiguration.error(CangJieBundle.message("dialog.message.no.working.directory.specified"))
        val cmd = run {
            val parsed = command.let { ParsedCommand.parse(it) }
                ?: return CleanConfiguration.error(CangJieBundle.message("dialog.message.no.command.specified"))

            CjpmCommandLine(
                parsed.command,

                workingDirectory,
                parsed.additionalArguments,
                redirectInputFile,
                parsed.toolchain,
                emulateTerminal,
                env,
                requiredFeatures,
                allFeatures,
                withSudo
            )
        }


        val toolchain = project.toolchain
            ?: return CleanConfiguration.error(CangJieBundle.message("dialog.message.no.cangjie.toolchain.specified"))

        if (!toolchain.looksLikeValidToolchain()) {
            return CleanConfiguration.error(
                CangJieBundle.message(
                    "dialog.message.invalid.toolchain",
                    toolchain.presentableLocation
                )
            )
        }


        return CleanConfiguration.Ok(cmd, toolchain)
    }

    override fun canRunOn(target: TargetEnvironmentConfiguration): Boolean =
        target.runtimes.findByType(CjLanguageRuntimeConfiguration::class.java) != null

    override fun getDefaultLanguageRuntimeType(): LanguageRuntimeType<*>? =
        LanguageRuntimeType.EXTENSION_NAME.findExtension(
            CjLanguageRuntimeType::class.java
        )

    override fun getDefaultTargetName(): String? = options.remoteTarget

    override fun setDefaultTargetName(targetName: String?) {
        options.remoteTarget = targetName
    }


    override fun getConfigurationEditor(): SettingsEditor<out RunConfiguration> {
//        创建
        val group = SettingsEditorGroup<CjpmCommandConfiguration>()
//        runConfigEditor.resetSdks()
        group.addEditor(
//            ExecutionBundle.message("run.configuration.configuration.tab.title"),
            "cjpm", CjpmCommandConfigurationEditor(project)
        )

//        group.addEditor(ExecutionBundle.message("logs.tab.title"), LogConfigurationPanel())
        return group
    }

    private var isRedirectInput: Boolean = false
    private var redirectInputPath: String? = null
    override fun isRedirectInput(): Boolean = isRedirectInput

    override fun setRedirectInput(value: Boolean) {
        isRedirectInput = value
    }

    override fun getRedirectInputPath(): String? = redirectInputPath

    override fun setRedirectInputPath(value: String?) {
        redirectInputPath = value
    }


    override fun writeExternal(element: Element) {
        super.writeExternal(element)
        env.writeExternal(element)
    }

    override fun readExternal(element: Element) {
        super.readExternal(element)
        env = EnvironmentVariablesData.readExternal(element)

    }

    companion object {
        fun findCjpmProject(project: Project, cmd: String, workingDirectory: Path?): CjpmProject? = findCjpmProject(
            project, ParametersListUtil.parse(cmd), workingDirectory
        )

        fun findCjpmProject(project: Project, additionalArgs: List<String>, workingDirectory: Path?): CjpmProject? {
            val cjpmProjects = project.cjpmProjects
            cjpmProjects.allProjects.singleOrNull()?.let { return it }
//TODO 严重问题 需要修改
//            没有--manifest-path
            val manifestPath = run {
                val idx = additionalArgs.indexOf("--manifest-path")
                if (idx == -1) return@run null
                additionalArgs.getOrNull(idx + 1)?.let { Paths.get(it) }
            }

            for (dir in listOfNotNull(manifestPath?.parent, workingDirectory)) {
                LocalFileSystem.getInstance().findFileByIoFile(dir.toFile())
                    ?.let { cjpmProjects.findProjectForFile(it) }
                    ?.let { return it }
            }
            return null
        }

    }
}


data class ParsedCommand(
    val command: String,
    val toolchain: String?,
    val additionalArguments: MutableList<String>
) {

    companion object {
        fun parse(rawCommand: String): ParsedCommand? {
            val args = ParametersListUtil.parse(rawCommand)
            val command = args.firstOrNull { !it.startsWith("+") } ?: return null
            val toolchain = args.firstOrNull()?.takeIf { it.startsWith("+") }?.removePrefix("+")
            val additionalArguments = args.drop(args.indexOf(command) + 1)
            return ParsedCommand(command, toolchain, additionalArguments.toMutableList())
        }
    }
}
